#ifndef __THREAD
#define __THREAD

#include <windows.h>
#include "..\River\River.h"
#include "..\Log\Log.h"
#include <queue>

DWORD WINAPI worker_thread(LPVOID parameter);
DWORD WINAPI listener_thread(LPVOID parameter);
DWORD WINAPI sender_thread(LPVOID parameter);

enum ThreadType
{
	SERVER,
	CLIENT,
	PASS
};

class Thread;

struct ThreadObjects
{
	River * river;
	Thread * serverthread;
	Thread * clientthread;
};

class Thread
{
private:
	int m_numthreads;
	LPVOID m_threadobject;
	ThreadType m_type;
	ThreadObjects m_threadobjects;
	queue<packet *> m_queue;
	Log m_log;

	int m_count;

public:
	CRITICAL_SECTION cs;
	HANDLE sem;

	Thread()
	{
		InitializeCriticalSection(&cs);
		m_threadobject = this;
		m_numthreads = 1;
		m_count = 0;		
	}
	
	virtual ~Thread() 
	{
		TRACE(5, "Thread Destructor called\n");
	}

	virtual bool process(packet * p) { return true; }

	void TRACE(int level, char * msg)
	{
		m_log.TRACE(level, msg);
	}

	void queue(queue<packet *> q)
	{
		m_queue = q;
	}

	int count()
	{
		return m_count;
	}

	void push(packet * p)
	{
		char message[1024];
		m_log.TRACE(1, "Push onto queue\n");
		EnterCriticalSection(&cs);
		sprintf(message, "%p Critical Section entered.\n", &cs);
		m_log.TRACE(1, message);
		if (p == NULL)
			m_log.TRACE(1, "Packet empty\n");
		else
		{
			m_queue.push(p);
			m_count++;
		}
		LeaveCriticalSection(&cs);
		sprintf(message, "%p Critical Section left.\n", &cs);
		m_log.TRACE(1, message);
		releasesemapore();
	}

	packet * pop()
	{
		char message[1024];
		packet * p = NULL;
		//m_log.TRACE(1, "Pop off of queue\n");
		EnterCriticalSection(&cs);
		sprintf(message, "%p Critical Section entered.\n", &cs);
		//m_log.TRACE(1, message);
		if (m_count != 0)
		{
			p = m_queue.front();
			m_queue.pop();
			m_count--;
		}
		LeaveCriticalSection(&cs);
		sprintf(message, "%p Critical Section left.\n", &cs);
		m_log.TRACE(1, message);
		return p;
	}

	packet * exitpacket()
	{
		packet * p = new packet();
		p->Header.Type = EXIT;
		return p;
	}

	void type(ThreadType t)
	{
		m_type = t;
	}

	void numthreads(int num)
	{
		m_numthreads = num;
	}

	void threadobject(LPVOID river, LPVOID clientthread, LPVOID serverthread)
	{
		m_threadobjects.river = (River *)river;
		m_threadobjects.clientthread = (Thread *)clientthread;
		m_threadobjects.serverthread = (Thread *)serverthread;
	}

	void createsemapores()
	{
		sem = CreateSemaphore(NULL, 0, m_numthreads, NULL);
	}

	void releasesemapore()
	{
		ReleaseSemaphore(sem, 1, NULL);
	}

	void start()
	{
		createsemapores();
		switch(m_type)
		{
		case SERVER:
			for (int i = 0; i < m_numthreads; i++)
				CreateThread(NULL,0, worker_thread, (LPVOID)m_threadobject, 0, NULL);
			break;
		case CLIENT:
			CreateThread(NULL,0, listener_thread, (LPVOID)&m_threadobjects, 0, NULL);
			Sleep(1000);
			CreateThread(NULL,0, sender_thread, (LPVOID)&m_threadobjects, 0, NULL);
			break;
		}

	}
};

DWORD WINAPI worker_thread(LPVOID parameter)
{
	Thread * threadobject = (Thread *)parameter;
	threadobject->TRACE(5, "Worker Thread Created\n");
	DWORD res;
	for (;;)
	{
		res = WaitForSingleObject(threadobject->sem, INFINITE);
		packet * p = NULL;
		if (threadobject->count() > 0)
			p = threadobject->pop();
		if (p != NULL)
		{
			threadobject->process(p);
		}
		if (threadobject->count() > 0)
			threadobject->releasesemapore();
	}
	return 1;
}

DWORD WINAPI listener_thread(LPVOID parameter)
{
	ThreadObjects * threadobjects = (ThreadObjects *)parameter;
	threadobjects->river->TRACE(5, "Listener Thread Created\n");
	char message[1024];
    int offset = 0, bufferoffset = 0;
    int numbytes = 0;
    char buf[BUFFER_SIZE * 10], runoff[BUFFER_SIZE];
    while (1)
    {
        memset(buf, 0, BUFFER_SIZE + bufferoffset);
        if (bufferoffset > 0)
        {
            memcpy(buf, runoff, bufferoffset);
        }
        offset = 0;
		numbytes = threadobjects->river->recv(buf + bufferoffset,PACKET_SIZE + 1);
		memset(message, 0, 1024);
		sprintf(message, "Recieved %d off the network.\n", numbytes);
		threadobjects->river->TRACE(5, message);

        if (numbytes < 0)
		{
			sprintf(message, "Recv error.\n");
			threadobjects->river->TRACE(5, message);
			packet * p = threadobjects->clientthread->exitpacket();
			p->Header.object = (LPVOID)threadobjects->clientthread;
			threadobjects->serverthread->push(p);
            return 0;
		}

		if (numbytes < PACKET_SIZE)
		{
			sprintf(message, "Recv error -- did not recieve a full packet.\n");
			threadobjects->river->TRACE(5, message);
			numbytes = 0;
		}
		else
		{
			packet * p = new packet();
			memcpy(&p->Header, buf, sizeof(pHeader));
			memcpy(p->msg, buf + sizeof(pHeader), numbytes - sizeof(pHeader));
			p->Header.object = (LPVOID)threadobjects->clientthread;
			threadobjects->serverthread->push(p);
		}
    }
	return 1;
}

DWORD WINAPI sender_thread(LPVOID parameter)
{
	ThreadObjects * threadobjects = (ThreadObjects *)parameter;
	threadobjects->river->TRACE(5, "Sender Thread Created\n");
	DWORD res;
	for (;;)
	{
		res = WaitForSingleObject(threadobjects->clientthread->sem, INFINITE);
		packet * p = threadobjects->clientthread->pop();
		if (p->Header.Type == EXIT)
		{
			delete p;
			return 0;
		}			
		else
			threadobjects->river->send(p);
		delete p;
	}
	return 1;
}

#endif